#include "mk20dx128.h"
#include <stdint.h>
// #include "HardwareSerial.h"

// The EEPROM is really RAM with a hardware-based backup system to
// flash memory.  Selecting a smaller size EEPROM allows more wear
// leveling, for higher write endurance.  If you edit this file,
// set this to the smallest size your application can use.  Also,
// due to Freescale's implementation, writing 16 or 32 bit words
// (aligned to 2 or 4 byte boundaries) has twice the endurance
// compared to writing 8 bit bytes.
//
#define EEPROM_SIZE 2048

// Writing unaligned 16 or 32 bit data is handled automatically when
// this is defined, but at a cost of extra code size.  Without this,
// any unaligned write will cause a hard fault exception!  If you're
// absolutely sure all 16 and 32 bit writes will be aligned, you can
// remove the extra unnecessary code.
//
#define HANDLE_UNALIGNED_WRITES

				// Minimum EEPROM Endurance
				// ------------------------
#if (EEPROM_SIZE == 2048)	// 35000 writes/byte or 70000 writes/word
  #define EEESIZE 0x33
#elif (EEPROM_SIZE == 1024)	// 75000 writes/byte or 150000 writes/word
  #define EEESIZE 0x34
#elif (EEPROM_SIZE == 512)	// 155000 writes/byte or 310000 writes/word
  #define EEESIZE 0x35
#elif (EEPROM_SIZE == 256)	// 315000 writes/byte or 630000 writes/word
  #define EEESIZE 0x36
#elif (EEPROM_SIZE == 128)	// 635000 writes/byte or 1270000 writes/word
  #define EEESIZE 0x37
#elif (EEPROM_SIZE == 64)	// 1275000 writes/byte or 2550000 writes/word
  #define EEESIZE 0x38
#elif (EEPROM_SIZE == 32)	// 2555000 writes/byte or 5110000 writes/word
  #define EEESIZE 0x39
#endif

void eeprom_initialize(void)
{
	uint32_t count=0;
	uint16_t do_flash_cmd[] = {
		0xf06f, 0x037f, 0x7003, 0x7803,
		0xf013, 0x0f80, 0xd0fb, 0x4770};
	uint8_t status;

	if (FTFL_FCNFG & FTFL_FCNFG_RAMRDY) {
		// FlexRAM is configured as traditional RAM
		// We need to reconfigure for EEPROM usage
		FTFL_FCCOB0 = 0x80; // PGMPART = Program Partition Command
		FTFL_FCCOB4 = EEESIZE; // EEPROM Size
		FTFL_FCCOB5 = 0x03; // 0K for Dataflash, 32K for EEPROM backup
		__disable_irq();
		// do_flash_cmd() must execute from RAM.  Luckily the C syntax is simple...
		(*((void (*)(volatile uint8_t *))((uint32_t)do_flash_cmd | 1)))(&FTFL_FSTAT);
		__enable_irq();
		status = FTFL_FSTAT;
		if (status & 0x70) {
			FTFL_FSTAT = (status & 0x70);
			return; // error
		}
	}
	// wait for eeprom to become ready (is this really necessary?)
	while (!(FTFL_FCNFG & FTFL_FCNFG_EEERDY)) {
		if (++count > 20000) break;
	}
}

#define FlexRAM ((uint8_t *)0x14000000)

uint8_t eeprom_read_byte(const uint8_t *addr)
{
	uint32_t offset = (uint32_t)addr;
	if (offset >= EEPROM_SIZE) return 0;
	return FlexRAM[offset];
}

uint16_t eeprom_read_word(const uint16_t *addr)
{
	uint32_t offset = (uint32_t)addr;
	if (offset >= EEPROM_SIZE-1) return 0;
	return *(uint16_t *)(&FlexRAM[offset]);
}

uint32_t eeprom_read_dword(const uint32_t *addr)
{
	uint32_t offset = (uint32_t)addr;
	if (offset >= EEPROM_SIZE-3) return 0;
	return *(uint32_t *)(&FlexRAM[offset]);
}


static void flexram_wait(void)
{
	while (!(FTFL_FCNFG & FTFL_FCNFG_EEERDY)) {
		// TODO: timeout
	}
}

void eeprom_write_byte(uint8_t *addr, uint8_t value)
{
	uint32_t offset = (uint32_t)addr;

	if (offset >= EEPROM_SIZE) return;
	if (FlexRAM[offset] != value) {
		FlexRAM[offset] = value;
		flexram_wait();
	}
}

void eeprom_write_word(uint16_t *addr, uint16_t value)
{
	uint32_t offset = (uint32_t)addr;

	if (offset >= EEPROM_SIZE-1) return;
#ifdef HANDLE_UNALIGNED_WRITES
	if ((offset & 1) == 0) {
#endif
		if (*(uint16_t *)(&FlexRAM[offset]) != value) {
			*(uint16_t *)(&FlexRAM[offset]) = value;
			flexram_wait();
		}
#ifdef HANDLE_UNALIGNED_WRITES
	} else {
		if (FlexRAM[offset] != value) {
			FlexRAM[offset] = value;
			flexram_wait();
		}
		if (FlexRAM[offset + 1] != (value >> 8)) {
			FlexRAM[offset + 1] = value >> 8;
			flexram_wait();
		}
	}
#endif
}

void eeprom_write_dword(uint32_t *addr, uint32_t value)
{
	uint32_t offset = (uint32_t)addr;

	if (offset >= EEPROM_SIZE-3) return;
#ifdef HANDLE_UNALIGNED_WRITES
	switch (offset & 3) {
	case 0:
#endif
		if (*(uint32_t *)(&FlexRAM[offset]) != value) {
			*(uint32_t *)(&FlexRAM[offset]) = value;
			flexram_wait();
		}
		return;
#ifdef HANDLE_UNALIGNED_WRITES
	case 2:
		if (*(uint16_t *)(&FlexRAM[offset]) != value) {
			*(uint16_t *)(&FlexRAM[offset]) = value;
			flexram_wait();
		}
		if (*(uint16_t *)(&FlexRAM[offset + 2]) != (value >> 16)) {
			*(uint16_t *)(&FlexRAM[offset + 2]) = value >> 16;
			flexram_wait();
		}
		return;
	default:
		if (FlexRAM[offset] != value) {
			FlexRAM[offset] = value;
			flexram_wait();
		}
		if (*(uint16_t *)(&FlexRAM[offset + 1]) != (value >> 8)) {
			*(uint16_t *)(&FlexRAM[offset + 1]) = value >> 8;
			flexram_wait();
		}
		if (FlexRAM[offset + 3] != (value >> 24)) {
			FlexRAM[offset + 3] = value >> 24;
			flexram_wait();
		}
	}
#endif
}


/*
void do_flash_cmd(volatile uint8_t *fstat)
{
	*fstat = 0x80;
	while ((*fstat & 0x80) == 0) ; // wait
}
00000000 <do_flash_cmd>:
   0:	f06f 037f 	mvn.w	r3, #127	; 0x7f
   4:	7003      	strb	r3, [r0, #0]
   6:	7803      	ldrb	r3, [r0, #0]
   8:	f013 0f80 	tst.w	r3, #128	; 0x80
   c:	d0fb      	beq.n	6 <do_flash_cmd+0x6>
   e:	4770      	bx	lr
*/

